package org.vaadin.nf4v.ext.config.beforenav;

import org.vaadin.nf4v.core.config.ContextConfig;
import org.vaadin.nf4v.core.config.ContextConfigurationException;
import org.vaadin.nf4v.core.config.PageConfig;
import org.vaadin.nf4v.core.config.process.NavigationConfigProcessor;
import org.vaadin.nf4v.core.config.process.parser.PageConfigParser;
import org.vaadin.nf4v.core.navigation.NavigationRequest;
import org.vaadin.nf4v.core.navigation.event.NavigationState;
import org.vaadin.nf4v.ext.config.util.AnnotationUtil;

import java.lang.reflect.Method;

/**
 * Page method @BeforeNavigation annotation parser.
 * Searches for methods marked with @BeforeNavigation annotation.
 * Allowed method parameters are:
 * 1. no parameters
 * 2. NavigationRequest
 * <p/>
 * Only one method may be annotated in page class.
 */
public class BeforeNavigationAnnotationParser implements PageConfigParser {
    public static final String BEFORE_NAVIGATION_METHOD_EVENTS = "org.vaadin.nf4v.beforenav.METHOD_EVENTS";
    public static final String BEFORE_NAVIGATION_METHOD_PARAMS_KEY = "org.vaadin.nf4v.beforenav.METHOD_PARAMS";
    public static final String BEFORE_NAVIGATION_METHOD_KEY = "org.vaadin.nf4v.beforenav.METHOD";

    @Override
    public void parse(NavigationConfigProcessor navigationConfigProcessor, ContextConfig contextConfig, PageConfig pageConfig)
            throws ContextConfigurationException {
        Class clazz = pageConfig.getPageClass();
        Method method = AnnotationUtil.getAnnotatedMethod(BeforeNavigation.class, clazz);
        if (method != null) {
            BeforeNavigation ann = method.getAnnotation(BeforeNavigation.class);
            if (ann != null) {
                if (ann.events() != null && ann.events().length > 0) {
                    for (NavigationState state : ann.events()) {
                        if (state.ordinal() >= NavigationState.AFTER_NAVIGATION.ordinal()
                                || state.ordinal() <= NavigationState.CONTEXT_ACTIVATED.ordinal()) {
                            throw new ContextConfigurationException("Not allowed navigation state " + state +
                                    " in before navigation listener: " + clazz.getSimpleName() +
                                    "#" + method.getName() + " for page class " + pageConfig.getPageClass().getSimpleName());
                        }
                    }
                    pageConfig.setAttribute(BEFORE_NAVIGATION_METHOD_EVENTS, ann.events());
                }
                pageConfig.setAttribute(BEFORE_NAVIGATION_METHOD_PARAMS_KEY, analyzeParamType(clazz, method));
                pageConfig.setAttribute(BEFORE_NAVIGATION_METHOD_KEY, method);
            }
        }

    }

    /**
     * @param clazz  page class (or super class, which contains method)
     * @param method annotated method
     * @return parameter type of annotated method
     */
    protected MethodType analyzeParamType(Class clazz, Method method) {
        Class[] params = method.getParameterTypes();
        if (params.length > 1) {
            throw new ContextConfigurationException("Wrong navigation listener method " + clazz.getSimpleName() +
                    "#" + method.getName() + ". Allowed parameters are: NavigationRequest or no params");
        }
        if (params.length == 0) {
            return MethodType.NO_PARAMS;
        }
        Class pclazz = params[0];
        if (pclazz.isAssignableFrom(NavigationRequest.class)) {
            return MethodType.REQUEST;
        }
        throw new ContextConfigurationException("Wrong parameter type " + pclazz.getSimpleName() + " for listener method " +
                clazz.getSimpleName() + "#" + method.getName());
    }
}
